/**
* ClassPath - Represents a classpath for an application
*
* Copyright (c) 2002
* Marty Phelan, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.taursys.tools.util;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Enumeration;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.zip.ZipFile;
import com.taursys.debug.Debug;
/**
* ClassPath represents a classpath for an application. It consists of a
* set of Strings that contain the path to classes and resources needed by
* the application. The classpath can contain directory references where
* classes can be found and jar(or zip) file names which contain classes.
* <p>
* This class provides a number of utility methods for classpath related
* activities.
* @author Marty Phelan
* @version 1.0
*/
public class ClassPath {
private ArrayList paths = new ArrayList();
private TreeSet entries;
private DefaultMutableTreeNode classTree = null;
private DefaultMutableTreeNode packageTree = null;
/**
* Constructs a new empty ClassPath
*/
public ClassPath() {
}
/**
* Constructs a new ClassPath based on the given String representation.
*/
public ClassPath(String classPath) {
setClassPathString(classPath);
}
/**
* Set this ClassPath from the given String representation. The given
* String classPath will be broken into individual paths. They must be
* separated by a semicolon.
* @param classPath is a set of paths separated by semicolons.
*/
public void setClassPathString(String classPath) {
StringTokenizer tokens = new StringTokenizer(classPath, ";");
while (tokens.hasMoreTokens()) {
addPath(tokens.nextToken());
}
}
/**
* Get the String representation of this classpath. The classpath will
* consist of all the individiual paths concatenated and separated by
* semicolons.
* @return the String representation of this classpath.
*/
public String getClassPathString() {
String path = null;
Iterator iter = paths.iterator();
while (iter.hasNext()) {
if (path == null)
path = "";
else
path += ";";
path += (String)iter.next();
}
return path;
}
/**
* Add a path to this classpath. If the given path already exists in this
* classpath, no action will be taken.
* @param path to add to this classpath.
*/
public void addPath(String path) {
if (!paths.contains(path))
paths.add(path);
resetEntries();
}
/**
* Remove a path from this classpath. If the given path is not found
* in this classpath, no action will be taken.
* @param path to remove from this classpath.
*/
public void removePath(String path) {
paths.remove(path);
resetEntries();
}
/**
* Set the internal ArrayList of paths for this ClassPath.
* @param newPaths the internal ArrayList of paths for this ClassPath.
*/
public void setPaths(ArrayList newPaths) {
paths = newPaths;
resetEntries();
}
/**
* Get a copy of the paths for this ClassPath.
* @return a copy of the paths for this ClassPath.
*/
public ArrayList getPaths() {
return new ArrayList(paths);
}
/**
* Get a Tree consisting of all classes based on this ClassPath.
*/
public DefaultMutableTreeNode getClassTree() {
if (classTree == null)
classTree = getTree(new ClassFilter(), true);
return classTree;
}
/**
* Get a Tree consisting of all packages based on this ClassPath.
*/
public DefaultMutableTreeNode getPackageTree() {
if (packageTree == null)
packageTree = getTree(new PackageFilter(), false);
return packageTree;
}
/**
* Get the internal set of entries -- for testing purposes only.
* This is a sorted unique set of all entries obtained from resources
* in the paths.
* @return a TreeSet of internal entries
*/
public TreeSet getEntries() {
// Make sure entries are set
if (entries == null)
getEntriesInPath();
return entries;
}
/**
* Reset the collection of paths to empty. Also resets internal cached entries.
*/
public void resetPaths() {
paths.clear();
resetEntries();
}
// ==================================================================
// Tree List Methods
// ==================================================================
/**
* FileFilter for classes only (excluding inner classes)
*/
protected class ClassFilter implements FileFilter {
public boolean accept(File file) {
return file.getName().toLowerCase().endsWith(".class")
&& file.getName().indexOf('$') == -1;
}
}
/**
* FileFilter for packages only - no files
*/
protected class PackageFilter implements FileFilter {
public boolean accept(File file) {
return false;
}
}
/**
* Get the String representation of given path
*/
protected String getTreePath(DefaultMutableTreeNode node) {
String path = "";
while (node.getParent() != null) {
path = node.getUserObject() + "/" + path;
node = (DefaultMutableTreeNode)node.getParent();
}
return path;
}
private String removeFileType(String fileName) {
int pos = fileName.lastIndexOf(".");
if (pos != -1) {
return fileName.substring(0, pos);
} else {
return fileName;
}
}
/**
* Get a Tree consisting of all files matching given filter.
* @return DefaultMutableTreeNode root of tree containing directories and files.
*/
public DefaultMutableTreeNode getTree(FileFilter filter, boolean removeFileType) {
DefaultMutableTreeNode top = new DefaultMutableTreeNode(
new ClassPathNode());
DefaultMutableTreeNode parentNode = top;
DefaultMutableTreeNode child = null;
int level = 1;
int parentLength = 0;
// Make sure entries are set
if (entries == null)
getEntriesInPath();
Iterator iter = entries.iterator();
while (iter.hasNext()) {
String item = (String)iter.next();
String qualifiedName = item.replace('/', '.');
StringTokenizer tokens = new StringTokenizer(item, "/");
int tokenCount = tokens.countTokens();
// check if we have gone up one or more levels
if (tokenCount < level) {
String parentPath = null;
do {
level--;
parentNode = (DefaultMutableTreeNode)parentNode.getParent();
parentPath = getTreePath(parentNode);
parentLength = parentPath.length();
} while (!item.startsWith(parentPath));
// else check if we have gone down a level
} else if (tokenCount > level) {
level++;
parentNode = child;
parentLength = getTreePath(parentNode).length();
}
// Check whether to create directory node
if (item.endsWith("/")) {
if (parentLength < 0) {
// error condition
Debug.debug("ClassPath.getTree Index out of range: item="
+ item + " parentLength=" + parentLength + " level=" + level);
} else {
item = item.substring(parentLength, item.length() - 1);
}
child = new DefaultMutableTreeNode(
new ClassPathNode(item, qualifiedName, ClassPathNode.TYPE_DIR));
parentNode.add(child);
// Else if it is an acceptable file, create file node
} else if (filter.accept(new File(item))) {
if (parentLength < 0) {
// error condition
Debug.debug("ClassPath.getTree Index out of range: item="
+ item + " parentLength=" + parentLength + " level=" + level);
} else {
item = item.substring(parentLength);
}
if (removeFileType) {
item = removeFileType(item);
qualifiedName = removeFileType(qualifiedName);
}
child = new DefaultMutableTreeNode(
new ClassPathNode(item, qualifiedName, ClassPathNode.TYPE_FILE));
parentNode.add(child);
}
}
return top;
}
// ==================================================================
// Entries List Methods
// ==================================================================
/**
* Reset the cached entries and trees, forcing a rebuild from the current paths.
* The internal entries are purged. They will be rebuilt by the
* next operation that needs them.
*/
protected void resetEntries() {
entries = null;
classTree = null;
packageTree = null;
}
/**
* Add missing entries to entries TreeSet. If a zip or jar file is not
* correct, initial paths may be missing from the entries table. An example
* would be: <code>com/sun/tools/jdi/resources/jdi.properties</code> followed
* by <code>com/sun/xml/parser/</code>. It is missing the entry
* <code>com/sun/xml/</code>.
*/
protected void addMissingEntries() {
ArrayList missingEntries = new ArrayList();
// for each entry, ensure that its parent directories are present
Iterator iter = entries.iterator();
while (iter.hasNext()) {
String item = (String)iter.next();
int lastSlash = item.lastIndexOf('/');
// only process items which contain slashes
if (lastSlash != -1) {
StringTokenizer tokens = new StringTokenizer(item.substring(0, lastSlash),"/");
// ensure that each directory is present else add it
String dir = "";
while (tokens.hasMoreTokens()) {
dir += tokens.nextToken() + "/";
if (!entries.contains(dir)) {
missingEntries.add(dir);
}
}
}
}
entries.addAll(missingEntries);
}
/**
* Set the internal entries TreeSet for testing purposes only.
* @param entries the internal entries TreeSet for testing purposes only.
*/
protected void setInternalEntries(TreeSet entries) {
this.entries = entries;
}
protected void getEntriesInPath() {
entries = new TreeSet();
Iterator iter = paths.iterator();
while (iter.hasNext()) {
String path = (String)iter.next();
File file = new File(path);
String fileName = file.getName().toLowerCase();
// process as archive or directory
if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) {
processArchive(file);
} else {
processDirectory(file.getAbsolutePath().length()+1, file);
}
}
addMissingEntries();
}
private void processDirectory(int prefix, File dir) {
if(!dir.isDirectory() )
return;
// store directory only if not root
if (dir.getAbsolutePath().length() > prefix) {
String relative = dir.getAbsolutePath().substring(prefix);
if (!relative.endsWith("/"))
relative += "/";
entries.add(relative);
}
// process entries
File[] sub = dir.listFiles();
for( int i=0; i<sub.length; ++i ) {
processDirectory(prefix, sub[i]);
processFile(prefix, sub[i]);
}
}
private void processFile(int prefix, File f) {
if( !f.isFile() )
return;
String relative = f.getAbsolutePath().substring(prefix);
entries.add(relative);
}
private void processArchive(File archive) {
try {
ZipFile zf = new ZipFile(archive);
Enumeration e = zf.entries();
while( e.hasMoreElements() )
entries.add(e.nextElement().toString());
zf.close();
} catch(IOException ex) {
Debug.error("Error during process archive", ex);
}
}
}